/**
 *     AUtils - A collection of utility classes for the Android system.
Copyright (C) 2009 Martin Vysny

This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU Lesser General Public License
along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
package com.jm.jisho.dict.util.android;

import android.content.Context;
import java.io.ByteArrayOutputStream;
import java.io.Closeable;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.util.Properties;

import android.util.Log;

/**
 * Contains misc utility methods.
 *
 * @author Martin Vysny
 */
public final class MiscUtils {

    private MiscUtils() {
        throw new AssertionError();
    }

    /**
     * Closes given closeable quietly. Any errors are logged as warnings to the
     * android log.
     *
     * @param in
     *            the closeable to close. Nothing is done if null.
     */
    public static void closeQuietly(Closeable in) {
        if (in == null) {
            return;
        }
        try {
            in.close();
        } catch (Exception ex) {
            Log.w("MiscUtils", "Failed to close closeable", ex);
        }
    }

    /**
     * Opens a class-loader resource. Fails if the resource does not exist.
     *
     * @param resName
     *            the resource name
     * @param cl
     *            class-loader to use. Use {@link android.content.Context#getClassLoader()} to obtain one.
     * @return an opened stream
     * @throws java.io.IOException
     *             if the resource does not exist.
     */
    public static InputStream openResource(String resName, final ClassLoader cl)
            throws IOException {
        final InputStream result = cl.getResourceAsStream(resName);
        if (result == null) {
            throw new IOException("Failed to load resource '" + resName + "'");
        }
        return result;
    }

    /**
     * Checks if given string is null, empty or whitespace-only.
     *
     * @param str
     *            the string to check
     * @return true if given string is null, empty or consists of whitespaces
     *         only.
     */
    public static boolean isBlank(final String str) {
        return str == null || str.trim().length() == 0;
    }

    /**
     * Checks if given string array is null, empty or contains whitespace-only
     * strings only.
     *
     * @param str
     *            the string to check
     * @return true if given string is null, empty or consists of whitespaces
     *         only.
     */
    public static boolean isBlank(final String[] str) {
        if (str == null || str.length == 0) {
            return true;
        }
        for (final String s : str) {
            if (!isBlank(s)) {
                return false;
            }
        }
        return true;
    }

    /**
     * Returns stacktrace of given exception.
     *
     * @param ex
     *            the exception
     * @return stacktrace as string.
     */
    public static String getStacktrace(Exception ex) {
        final StringWriter sw = new StringWriter();
        final PrintWriter pw = new PrintWriter(sw);
        ex.printStackTrace(pw);
        pw.close();
        return sw.toString();
    }

    /**
     * Loads property file from given input stream. The stream is always closed.
     *
     * @param in
     *            the stream to read from
     * @return properties instance
     * @throws java.io.IOException
     *             if i/o error occurs
     */
    public static Properties load(final InputStream in) throws IOException {
        try {
            final Properties result = new Properties();
            result.load(in);
            return result;
        } finally {
            closeQuietly(in);
        }
    }

    /**
     * Deletes given directory, including subdirectories.
     *
     * @param dir
     *            the directory to delete. For safety reasons it must start with
     *            /sdcard/aedict
     * @throws java.io.IOException
     */
    public static void deleteDir(final File dir) throws IOException {
        if (!dir.getPath().startsWith("/sdcard/")) {
            throw new IllegalArgumentException(dir + " does not start with /sdcard/");
        }
        if (!dir.exists()) {
            return;
        }
        if (!dir.isDirectory()) {
            throw new IllegalArgumentException(dir + " is not a directory");
        }
        for (final File f : dir.listFiles()) {
            if (f.isDirectory()) {
                deleteDir(f);
            } else {
                if (!f.delete()) {
                    throw new IOException("Failed to delete " + f.getAbsolutePath());
                }
            }
        }
        if (!dir.delete()) {
            throw new IOException("Failed to delete " + dir.getAbsolutePath());
        }
    }

    /**
     * Returns length in bytes of given file or directory.
     *
     * @param dir
     *            the directory to list. A directory length is set to be 4kb +
     *            lengths of all its children.
     * @return the directory length, 0 if the directory/file does not exist.
     */
    public static long getLength(final File dir) {
        if (!dir.exists()) {
            return 0;
        }
        if (dir.isFile()) {
            return dir.length();
        } else if (dir.isDirectory()) {
            long result = 4096;
            for (final File file : dir.listFiles()) {
                result += getLength(file);
            }
            return result;
        } else {
            return 0;
        }
    }

    /**
     * Checks if given character is an ascii letter (a-z, A-Z).
     *
     * @param c
     *            the character to check
     * @return true if the character is a letter, false otherwise.
     */
    public static boolean isAsciiLetter(char c) {
        return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z');
    }
    private static final int BUFSIZE = 8192;

    /**
     * Reads given file fully and returns its contents. The stream is always
     * closed.
     *
     * @param in
     *            read this stream. Always closed.
     * @return the byte contents of given stream.
     * @throws java.io.IOException
     *             on i/o error
     */
    public static byte[] readFully(final InputStream in) throws IOException {
        try {
            final byte[] buf = new byte[BUFSIZE];
            final ByteArrayOutputStream bout = new ByteArrayOutputStream();
            while (true) {
                final int bytesRead = in.read(buf);
                if (bytesRead < 0) {
                    break;
                }
                if (bytesRead == 0) {
                    continue;
                }
                bout.write(buf, 0, bytesRead);
            }
            return bout.toByteArray();
        } finally {
            closeQuietly(in);
        }
    }

    /**
     * A workaround around broken {@link Class#isEnum()} which returns false for more complex enums
     * (enums which define abstract methods and let each constant override the method).
     * @param clazz
     * @return
     */
    public static boolean isEnum(final Class<?> clazz) {
        if (clazz.isEnum()) {
            return true;
        }
        if (Enum.class.isAssignableFrom(clazz)) {
            return true;
        }
        return false;
    }
    /**
     * Contains all whitespace characters.
     */
    public static final String WHITESPACE_CHARACTERS = " \t\n\r\f";

    /**
     * Removes all whitespace character from the string.
     * @param string the string
     * @return string with whitespace characters removed.
     */
    public static String removeWhitespaces(final String string) {
        final StringBuilder result = new StringBuilder(string.length());
        for (int i = 0; i < string.length(); i++) {
            final char c = string.charAt(i);
            if (!Character.isWhitespace(c)) {
                result.append(c);
            }
        }
        return result.toString();
    }
}